#ifndef __LOG_HPP__
#define __LOG_HPP__

#include <iostream>
#include <filesystem>//c++17
#include <fstream>//c++11文件操作
#include <string>
#include <ctime>//获取时间戳
#include <sstream>
#include <unistd.h>
#include <memory>
#include "Mutex.hpp"

namespace LogModule
{
    #define SPE "\r\n"
    //将锁的命名空间展开
    using namespace MuteModule;
    //刷新策略:基于C++的多态
    //刷新测略的基类
    class Strategy
    {
    public:
        ~Strategy() = default;//让编译器自动生成默认的析构函数
        virtual void SyncLog(const std::string& message) = 0;//纯虚函数,派生类中必须实现该函数
    };

    //刷新到显示器上
    class ConsoleLogStrategy : public Strategy
    {
    public:
        ConsoleLogStrategy()
        {}
        void SyncLog(const std::string& message) override
        {
            LockGuard t(_mutex);
            std::cout << message << SPE;
        }
        ~ConsoleLogStrategy()
        {}
    private:
        Mutex _mutex;
    };


    #define PATH "./log"
    #define NAME "my.log"
    //刷新到普通文件当中
    class FileLogStrategy : public Strategy
    {
    public:
        FileLogStrategy(const std::string path = PATH, std::string name = NAME)
        : _path(path),
          _name(name)
        {
            //创建路径时要加所(这里不能使用匿名对象为什么会显示LockGuard没有默认构造)
            //因为编译器,把他看成了LockGuard mutex,因为LockGuard 没有默认构造所以会报错
            //解决方法1.显示制定姓名,我们下面用的方法
            //       2.使用{}初始化方式使其强制匹配构造函数

            LockGuard t{_mutex};
            //判断路径是否存在
            if(std::filesystem::exists(PATH))
            {
                //存在则退出
                return;
            }
            //不存在则创建路径
            try
            {
                std::filesystem::create_directories(PATH);
            }
            catch(const std::filesystem::filesystem_error& e)
            {
                std::cerr << e.what() << SPE;
            }
            
        }
        void SyncLog(const std::string& message) override
        {
            LockGuard t(_mutex);
            _file_path = _path +( _path.back() == '/' ? "" : "/" ) + _name;
            std::ofstream out(_file_path,std::ios::app);//以追加的方式打开文件
            //判断文件是否打开
            if(!out.is_open())
            {
                return;
            }
            //文件打开成功
            out << message << SPE;
            //关闭文件
            out.close();

        }
        ~FileLogStrategy()
        {}
    private:
        Mutex _mutex;
        std::string _file_path;
        std::string _path;
        std::string _name;

    };
    enum class LogLevel
    {
        DEBUG,
        INFO,
        WARNING,
        ERROR,
        FATAL
    };
    std::string LogLevelStr(LogLevel log)
    {
        switch(log)
        {
            case LogLevel::DEBUG:
                return "DEBUG";
            case LogLevel::INFO:
                return "INFO";
            case LogLevel::WARNING:
                return "WARNING";
            case LogLevel::ERROR:
                return "ERROR";
            case LogLevel::FATAL:
                return "FATAL";
            default:
                return "NULL";
            
        }
    }
    std::string time_create()
    {
        time_t curr = time(nullptr);
        struct tm curr_tm;
        localtime_r(&curr,&curr_tm);
        char buffer[128] = {0};
        snprintf(buffer,sizeof buffer,"%4d-%02d-%02d %02d:%02d:%02d",
                curr_tm.tm_year + 1900,
                curr_tm.tm_mon + 1,
                curr_tm.tm_mday,
                curr_tm.tm_hour,
                curr_tm.tm_min,
                curr_tm.tm_sec
        );
        return buffer;
    }
    class Logger
    {
    public:
        Logger()
        {
            EnableConsoleLogStrategy();
        }
        void EnableConsoleLogStrategy()
        {
            _fflush_strategy = std::make_unique<ConsoleLogStrategy>();
        }
        void EnableFileLogStrategy()
        {
            _fflush_strategy = std::make_unique<FileLogStrategy>();
        }
        class LogMessage
        {
        public:
            LogMessage(LogLevel& level,std::string& src_name, int line_number,Logger& logger)
            :_curr_time(time_create()),
             _level(level),
             _pid(getpid()),
             _src_name(src_name),
             _line_number(line_number),
             _logger(logger)
            {
                //左半部分日志
                std::stringstream ss;
                ss << "[" << _curr_time << "]" << " "
                   << "[" <<LogLevelStr(_level)  << "]" << " "
                   << "[" << _pid << "]" << " "
                   << "[" << _src_name << "]" << " "
                   << "[" << _line_number << "]" << "-";
                   _loginfo = ss.str();
            }
            //后半条消息
            template <typename T>
            LogMessage& operator<<(const T& info)
            {
                //右半部分日志
                std::stringstream ss;
                ss << info;
                _loginfo += ss.str();
                return *this;
            }
            ~LogMessage()
            {
                _logger._fflush_strategy->SyncLog(_loginfo);
            }
        private:
            std::string _curr_time;//时间
            LogLevel _level;//日志等级
            pid_t _pid;//进程pid
            std::string _src_name;//文件名
            int _line_number;//行号
            std::string _loginfo;//合并后的消息
            Logger& _logger;//用来调用刷新策略
        };
        //返回值必须是对象，因为里面用的是匿名对象，如果使用引用会触发权限放大问题,如果使用const引用会造成不能修改的问题
        //使用有名对象不能传引用返回,因为有名对象会被销毁
        LogMessage operator()(LogLevel level, std::string name, int line)
        {
            LogMessage t(level,name,line,*this);
            return t;
            //return LogMessage (level,name,line,*this);
        }

        ~Logger()
        {
            
        }
    private:
        //string可能间接包含了智能指针的头文件还是加一下比较好
        std::unique_ptr<Strategy> _fflush_strategy;//选择刷新策略
    };
    Logger logger;

    #define LOG(level) logger(level,__FILE__,__LINE__)//调用日志
    #define Enable_Console_Log_Strategy() logger.EnableConsoleLogStrategy()//显示器刷新策略
    #define Enable_File_Log_Strategy() logger.EnableFileLogStrategy()//文件刷新策略
}

#endif